<!DOCTYPE html>
<html lang="en">
<html><head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="author" content="寞踪">
    <title>Lotus时空证明源码梳理 &middot; 寞踪的技术博客</title>
	<meta name="description" content="最开心的博客 Lotus时空证明源码梳理。"/>

    
    <link href="https://xpmozong.gitee.io/css/bootstrap.min.css" rel="stylesheet">
    <link href="https://xpmozong.gitee.io/css/font-awesome.min.css" rel="stylesheet">
    <link href="https://xpmozong.gitee.io/css/animate.min.css" rel="stylesheet">
    <link href="https://xpmozong.gitee.io/css/blog.css" rel="stylesheet">
    <link href="https://xpmozong.gitee.io/css/responsive.css" rel="stylesheet">
    
	
		<link rel="shortcut icon" href="/images/favicon.ico">
	
</head><body><header id="header">
    <nav class="navbar navbar-inverse navbar-fixed-top" role="banner">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                    <span class="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a class="navbar-brand" href="/">
					
						<img src="/images/logo.png" alt="logo">
					
				</a>
            </div>

            <div class="collapse navbar-collapse navbar-right">
                <ul class="nav navbar-nav">
					
					
					
						
						
						<li  ><a href="/">首页</a></li>
						
					
						
						
						<li   class="active" ><a href="/posts">博客</a></li>
						
					
						
						
							<li class="dropdown ">
							    <a href="javascript:;" class="dropdown-toggle" data-toggle="dropdown">
									分类 <i class="fa fa-angle-down"></i>
								</a>
							    <ul class="dropdown-menu">
									
							        <li><a href="/categories/%E7%BD%91%E7%BB%9C%E5%AE%89%E5%85%A8">网络安全</a></li>
									
							        <li><a href="/categories/web3">web3</a></li>
									
							        <li><a href="/categories/lotus">lotus</a></li>
									
							    </ul>
							</li>
						
					
						
						
						<li  ><a href="/tags">标签</a></li>
						
					
						
						
						<li  ><a href="/about">关于我</a></li>
						
					
						
						
						<li  ><a href="/links">友情链接</a></li>
						
					
                </ul>
            </div>
        </div>
        
    </nav>
    
</header>

    <div class="page-title" style="background-image: url(https://xpmozong.gitee.io/images/page-title.jpg)">
        <h1>Single Blog</h1>
    </div>
    <section id="blog">
        <div class="blog container">
            <div class="row">
                <div class="col-md-8">
                    <div class="blog-item">
                        <div class="blog-content">
                            <h2>Lotus时空证明源码梳理</h2>
                            <div class="post-meta">
                                <p><i class="fa fa-clock-o"></i> October 20, 2022</p>
								<p>字数: 3122</p>
                            </div>
                            <p>lotus 的时空证明（PoSt）在两个地方会被调用该算法：Winning PoSt 和 Window PoSt。</p>
<h3 id="winning-post">Winning PoSt</h3>
<p>是矿工在出块时对已经提交的扇区进行证明，证明扇区保存的数据依然存在。</p>
<p>WinningPoSt是在获取出块权时，需要提供的PoSt证明。从所有有效的Sector中，抽取一个Sector，该Sector中的66条随机抽查的merkle path都能正确。</p>
<p>一切从出块开始 - lotus/miner/miner.go的Miner结构的mineOne函数。</p>
<pre tabindex="0"><code>func (m *Miner) mineOne(ctx context.Context, addr address.Address, base *MiningBase) (*types.BlockMsg, error) {
    mbi, err := m.api.MinerGetBaseInfo(ctx, addr, round, base.TipSet.Key())

    rand, err := lrand.DrawRandomness(rbase.Data, crypto.DomainSeparationTag_WinningPoStChallengeSeed, round, buf.Bytes())
    
    prand := abi.PoStRandomness(rand)

    postProof, err := m.epp.ComputeProof(ctx, mbi.Sectors, prand, round, nv)
}
</code></pre><p>其中，MinerGetBaseInfo函数是获取一些基本信息，其中包括需要抽取的sector信息。ComputeProof函数就是计算winningPoSt证明。</p>
<p><img src="go-rust-winningPoSt.jpg" alt="img"></p>
<p>中间的接口就不介绍了，直接看rust-fil-proofs提供的两个API函数。</p>
<p>抽查个数设置</p>
<p>Sector个数以及总的抽查的叶子个数的定义在rust-fil-proofs/filecoin-proofs/src/constants.rs中：</p>
<pre tabindex="0"><code> pub const WINNING_POST_CHALLENGE_COUNT: usize = 66;
 pub const WINNING_POST_SECTOR_COUNT: usize = 1;
</code></pre><p>也就是说，要从有效Sector中抽取一个Sector，并在这个Sector上抽查66个叶子节点。</p>
<p>Sector的抽查逻辑</p>
<p>generate_winning_post_sector_challenge函数实现了Sector的抽查逻辑。核心逻辑显然是如何抽查Sector？具体的逻辑在fallback::generate_sector_challenges的函数中：</p>
<pre tabindex="0"><code>let mut hasher = Sha256::new();
hasher.input(AsRef::&lt;[u8]&gt;::as_ref(&amp;prover_id));
hasher.input(AsRef::&lt;[u8]&gt;::as_ref(&amp;randomness));
hasher.input(&amp;n.to_le_bytes()[..]);

let hash = hasher.result();

let sector_challenge = LittleEndian::read_u64(&amp;hash.as_ref()[..8]);
let sector_index = sector_challenge % sector_set_len;
</code></pre><p>简单的说，就是把prover_id, 随机信息，抽查Sector的编号做sha256的hash计算，计算结果和当前有限的Sector个数取模。也就是sector_index就是最终抽查的Sector的id。</p>
<p>Challenge的叶子抽查逻辑</p>
<p>generate_winning_post在抽查的Sector形成的merkle树(replica_r_last)上，抽查叶子节点。抽查叶子节点的计算逻辑在fallback::generate_leaf_challenge的函数中：</p>
<pre tabindex="0"><code>let mut hasher = Sha256::new();
hasher.input(AsRef::&lt;[u8]&gt;::as_ref(&amp;randomness));
hasher.input(&amp;sector_id.to_le_bytes()[..]);
hasher.input(&amp;leaf_challenge_index.to_le_bytes()[..]);
let hash = hasher.result();

let leaf_challenge = LittleEndian::read_u64(&amp;hash.as_ref()[..8]);

let challenged_range_index = leaf_challenge % (pub_params.sector_size / NODE_SIZE as u64);
</code></pre><p>把随机信息，sector id和挑战叶子的编号进行hash计算。计算的结果和叶子的总个数取模。32G的Sector，叶子个数为1G个。</p>
<h3 id="window-post">Window PoSt</h3>
<p>是矿工在对应的周期内对已经提交的扇区进行证明，证明扇区保存的数据依然存在。</p>
<p>即每隔一段时间对提交的Sector的数据进行证明，证明Sector数据依然正确保存。</p>
<pre tabindex="0"><code>commit 382017dd33e9e818a51503893433628fab643dd3
Author: Alex North &lt;445306+anorth@users.noreply.github.com&gt;
Date:   Wed May 6 10:08:31 2020 +1000

    Set ConsensusMinerMinPower to 10TiB (#344)
</code></pre><p>###Miner’s State （矿工状态）</p>
<p>矿工的状态中存储了矿工信息，矿工的抵押信息以及Sector的各种状态。挑选和WindowPoSt相关的字段：</p>
<pre tabindex="0"><code> type State struct {
    ...
    // Information for all proven and not-yet-garbage-collected sectors.
    //
    // Sectors are removed from this AMT when the partition to which the
    // sector belongs is compacted.
    Sectors cid.Cid // Array, AMT[SectorNumber]SectorOnChainInfo (sparse)
    ProvingPeriodStart abi.ChainEpoch
    Deadlines cid.Cid
    ...
}
</code></pre><p>ProvingPeriodStart - 每次证明的起始区块时间</p>
<p>Deadlines - 每个挑战时间，分割成多个&quot;window&quot;，每个window又称为一个Deadline。WindowPoSt名称中的Window就是得名于此。</p>
<h4 id="11-proving-period证明间隔">1.1 Proving Period（证明间隔）</h4>
<p>WindowPoSt相关的配置定义在specs-actors/actors/builtin/miner/policy.go文件中：</p>
<pre tabindex="0"><code>var WPoStProvingPeriod = abi.ChainEpoch(builtin.EpochsInDay) // 24 hours PARAM_SPEC

var WPoStChallengeWindow = abi.ChainEpoch(30 * 60 / builtin.EpochDurationSeconds) // 30 minutes (48 per day) PARAM_SPEC

const WPoStPeriodDeadlines = uint64(48) // PARAM_SPEC

const WPoStChallengeLookback = abi.ChainEpoch(20) // PARAM_SPEC
</code></pre><p>WPoStProvingPeriod - 证明周期，每天需要证明一次。</p>
<p>WPoStChallengeWindow - 每半个小时是一个ChallengeWindow。每个证明周期，分为48个ChallengeWindow。</p>
<p>WPoStPeriodDeadlines - 每个ChallengeWindow，都是一个挑战窗口期，每个ChallengeWindow的结束时间都是Deadline。</p>
<p>WPoStChallengeLookback - 每个ChallengeWindow，从链上获取随机数信息，向前推算的区块时间。</p>
<p>简单的说，WindowPoSt的周期是一天，分成48个Window。在不同的Window时间段中的Sector的信息不同。每个Window都需要提交WindowPoSt的证明，Window中分配的Sector的个数决定了WindowPoSt证明的个数。具体的逻辑后面会介绍。</p>
<h4 id="12-deadline">1.2 Deadline</h4>
<p>Deadline的具体定义在specs-actors/actors/builtin/miner/deadlines.go中：</p>
<pre tabindex="0"><code> type DeadlineInfo struct {
     CurrentEpoch abi.ChainEpoch // Epoch at which this info was calculated.
     PeriodStart  abi.ChainEpoch // First epoch of the proving period (&lt;= CurrentEpoch).
     Index        uint64         // Current deadline index, in [0..WPoStProvingPeriodDeadlines), or WPoStProvingPeriodDeadlines if period elapsed.
     Open         abi.ChainEpoch // First epoch from which a proof may be submitted, inclusive (&gt;= CurrentEpoch).
     Close        abi.ChainEpoch // First epoch from which a proof may no longer be submitted, exclusive (&gt;= Open).
     Challenge    abi.ChainEpoch // Epoch at which to sample the chain for challenge (&lt; Open).
     FaultCutoff  abi.ChainEpoch // First epoch at which a fault declaration is rejected (&lt; Open).
 }
</code></pre><p>CurrentEpoch - 当前的区块时间</p>
<p>PeriodStart - 每个证明的起始的区块时间</p>
<p>Index - Deadline，也就是window的编号，0～47</p>
<p>Open - 在该window中，证明可以提交的起始时间</p>
<p>Close - 在该window中，证明可以提交的最后时间</p>
<p>Challenge - 该window中采用的随机数的计算范围</p>
<p>FaultCutoff - 只有在这个区块时间之前，才能申明错误区块</p>
<p>ComputeProvingPeriodDeadline函数在给定证明起始区块时间和当前区块时间的情况下，计算相应的Deadline：</p>
<pre tabindex="0"><code> func ComputeProvingPeriodDeadline(periodStart, currEpoch abi.ChainEpoch) *DeadlineInfo {
     periodProgress := currEpoch - periodStart
         ...
 
     deadlineIdx := uint64(periodProgress / WPoStChallengeWindow)
     if periodProgress &lt; 0 { // Period not yet started.
         deadlineIdx = 0
     }
     deadlineOpen := periodStart + (abi.ChainEpoch(deadlineIdx) * WPoStChallengeWindow)
 
     return &amp;DeadlineInfo{
         CurrentEpoch: currEpoch,
         PeriodStart:  periodStart,
         Index:        deadlineIdx,
         Open:         deadlineOpen,
         Close:        deadlineOpen + WPoStChallengeWindow,
         Challenge:    deadlineOpen - WPoStChallengeLookback,
         FaultCutoff:  deadlineOpen - FaultDeclarationCutoff,
     }
 }
</code></pre><p>通过当前的区块时间以及起始时间，可以计算出deadlineIdx（也就是Deadline的编号）。有了编号，其他信息就比较容易推导。</p>
<h5 id="状态变化逻辑">状态变化逻辑</h5>
<p>WindowPoSt的状态逻辑包括两部分：每次windowPoSt的证明的起始时间的调整和需要证明的Sector信息的更新。</p>
<p>2.1 证明起始时间调整</p>
<p>证明的起始时间，在miner智能合约创建时初始化：</p>
<pre tabindex="0"><code>offset, err := assignProvingPeriodOffset(rt.Message().Receiver(), currEpoch, rt.Syscalls().HashBlake2b)
periodStart := nextProvingPeriodStart(currEpoch, offset)
</code></pre><p>assignProvingPeriodOffset, 通过HashBlake2b函数，随机生成在[0, WPoStProvingPeriod]的随机offset。</p>
<p>nextProvingPeriodStart函数逻辑如下：</p>
<pre tabindex="0"><code> func nextProvingPeriodStart(currEpoch abi.ChainEpoch, offset abi.ChainEpoch) abi.ChainEpoch {
     currModulus := currEpoch % WPoStProvingPeriod
     var periodProgress abi.ChainEpoch // How far ahead is currEpoch from previous offset boundary.
     if currModulus &gt;= offset {
         periodProgress = currModulus - offset
     } else {
         periodProgress = WPoStProvingPeriod - (offset - currModulus)
     }
 
     periodStart := currEpoch - periodProgress + WPoStProvingPeriod
     Assert(periodStart &gt; currEpoch)
     return periodStart
 }
</code></pre><p>简单的说，就是在offset的情况下，找出当前下一个证明的起始时间。</p>
<p>在确定证明的起始时间后，miner智能合约在每个证明周期的结束，都会检查证明的状态，并更新证明的起始时间：</p>
<pre tabindex="0"><code>func handleProvingPeriod(rt Runtime) {
    ...
    if deadline.PeriodStarted() {
       st.ProvingPeriodStart = st.ProvingPeriodStart + WPoStProvingPeriod
  }
  ...
  err = st.ClearPoStSubmissions()
  ...
}
</code></pre><p>2.2 Sector信息更新</p>
<p>在miner的智能合约中，NewSectors的更新由三个接口完成：</p>
<p><img src="Sectors-Update.jpg" alt="img"></p>
<p>proveCommitSector，在提交PoREP的commit的同时添加新的Sector信息到NewSectors中。terminateSectors，从NewSectors中删除Sector的信息。handleProvingPeriod周期性地将NewSectors的信息“分配”到Deadline中。简单的说，在miner的智能合约中，维护了所有需要证明的Sector的列表。在每次windowPoSt的开始时，会将这些Sector分配到不同的Window。</p>
<p>Sector分配</p>
<p>通过AssignNewSectors函数，将需要证明的Sector，分配到不同的Window（Deadline）：</p>
<p><a href="https://blog.csdn.net/StarLi2020/article/details/115820477">https://blog.csdn.net/StarLi2020/article/details/115820477</a></p>
<p>总结：</p>
<p>Window PoSt，每隔一段时间对提交的Sector的数据的存在性进行证明。每个证明的周期为一天。整个周期又分成48个Window。每个Window为半个小时。每个Window都需要进行windowPoSt的证明计算。证明的个数是window中的Sector个数除以partition的Sector个数。每个partition的证明是对partition中的所有Sector抽取10个叶子节点。</p>

                            
                            <div class="inner-meta block">
								
									
										<a href="https://xpmozong.gitee.io/tags/lotus">Lotus</a>
									
								
                            </div>
                        </div>
                    </div>
                    
					
                    <div class="paging">
                        
                          <div class="block previous"><a href="https://xpmozong.gitee.io/posts/lotus%E4%BB%BB%E5%8A%A1%E8%B0%83%E5%BA%A6%E6%BA%90%E7%A0%81%E6%A2%B3%E7%90%86/">&lt;- Lotus任务调度源码梳理</a></div>
                        
                        
                    </div>
					
                </div>
                
				
				<aside class="col-md-4">
					<div class="toc" id="mright">
						<div class="widget">
							<h3 class="widget-title section-title">目录</h3>
							<div class="row">
								<div class="col-sm-12 widget--toc">
									<nav id="TableOfContents">
  <ul>
    <li>
      <ul>
        <li><a href="#winning-post">Winning PoSt</a></li>
        <li><a href="#window-post">Window PoSt</a></li>
      </ul>
    </li>
  </ul>
</nav>
								</div>
							</div>
						</div>
					</div>
				</aside>
				
            </div>
            
        </div>
    </section>
    
<section id="bottom">
	<div class="container fadeInDown" data-wow-duration="1000ms" data-wow-delay="600ms">
		<div class="row">
			<div class="col-md-2">
				<a href="/" class="footer-logo">
					
						<img src="https://xpmozong.gitee.io/images/dark-logo.png" alt="logo">
					
				</a>
			</div>
			<div class="col-md-10">
				<div class="row">
					<div class="col-md-3 col-sm-6">
						<div class="widget">
							<h3>近期文章</h3>
							<ul>
								
								
									<li><a href="https://xpmozong.gitee.io/posts/lotus%E6%97%B6%E7%A9%BA%E8%AF%81%E6%98%8E%E6%BA%90%E7%A0%81%E6%A2%B3%E7%90%86/">Lotus时空证明源码梳理</a></li>
								
									<li><a href="https://xpmozong.gitee.io/posts/lotus%E4%BB%BB%E5%8A%A1%E8%B0%83%E5%BA%A6%E6%BA%90%E7%A0%81%E6%A2%B3%E7%90%86/">Lotus任务调度源码梳理</a></li>
								
									<li><a href="https://xpmozong.gitee.io/posts/lotus%E5%A4%8D%E5%88%B6%E8%AF%81%E6%98%8E%E6%BA%90%E7%A0%81%E6%A2%B3%E7%90%86/">Lotus复制证明源码梳理</a></li>
								
									<li><a href="https://xpmozong.gitee.io/posts/%E4%BB%80%E4%B9%88%E6%98%AF-xss-%E6%94%BB%E5%87%BB/">什么是 XSS 攻击？</a></li>
								
									<li><a href="https://xpmozong.gitee.io/posts/csrf%E6%94%BB%E5%87%BB%E6%98%AF%E4%BB%80%E4%B9%88/">CSRF攻击是什么？</a></li>
								
							</ul>
						</div>
					</div>
					
					<div class="col-md-3 col-sm-6">
						<div class="widget">
							<h3>文章归档</h3>
							<ul>
								
									
								
								
								
								
								
								<li><a href="/archives#2022.10">2022.10</a></li>
								
							</ul>
						</div>
					</div>
					

					<div class="col-md-3 col-sm-6">
						<div class="widget">
							<h3>文章分类</h3>
							<ul>
								
									<li><a href="/categories/lotus/">Lotus(3)</a></li>
								
									<li><a href="/categories/%E7%BD%91%E7%BB%9C%E5%AE%89%E5%85%A8/">网络安全(2)</a></li>
								
							</ul>
						</div>
					</div>
					
					<div class="col-md-3 col-sm-6">
						<div class="widget">
							<h3>文章标签</h3>
							<ul>
								
									<li><a href="/tags/lotus/">Lotus(3)</a></li>
								
									<li><a href="/tags/%E7%BD%91%E7%BB%9C%E5%AE%89%E5%85%A8/">网络安全(2)</a></li>
								
							</ul>
						</div>
					</div>
					
				</div>
			</div>
		</div>
	</div>
</section>


<footer id="footer" class="midnight-blue">
    <div class="container">
        <div class="row">
            <div class="col-sm-6">
                &copy; 
				
					2020 - 
				
				2022
				
					寞踪 All Rights Reserved. <br/>
				
				
				
				
		
				Built with <a href="https://gohugo.io/" target="_blank" rel="noopener">Hugo</a> <br />
				主题 <b><a href="https://gitee.com/xpmozong/xpmozong" target="_blank" rel="noopener" data-version="%!s(MISSING)">Wave</a></b> 由 <a href="https://gitee.com/xpmozong" target="_blank" rel="noopener">寞踪</a> 设计
            </div>
            <div class="col-sm-6">
                <ul class="pull-right">
					
					
					
						
						<li  >
							<a href="/">首页</a>
						</li>
                    
						
						<li   class="active" >
							<a href="/posts">博客</a>
						</li>
                    
						
						<li  >
							<a href="/categories">分类</a>
						</li>
                    
						
						<li  >
							<a href="/tags">标签</a>
						</li>
                    
						
						<li  >
							<a href="/about">关于我</a>
						</li>
                    
						
						<li  >
							<a href="/links">友情链接</a>
						</li>
                    
                </ul>
            </div>
        </div>
    </div>
</footer>

<script src="https://xpmozong.gitee.io/js/jquery.js "></script>
<script src="https://xpmozong.gitee.io/js/bootstrap.min.js "></script>
<script src="https://cdn.jsdelivr.net/npm/fuse.js/dist/fuse.js"></script>
<script src="https://xpmozong.gitee.io/js/search.js"></script>
<script src="https://xpmozong.gitee.io/js/main.js"></script></body>
</html>
